#pragma once

/*
* Functions to manage strings
*/

#include <string>
#include <vector>
#include <algorithm>
#include <sstream>


namespace bzbstring {


/*
 * Removes the trailing character if equal to <c> (defaults to newline)
 */
	
inline bool chomp(std::string &str, const char c = '\n') {

	if(str == "")
		return false;

	const size_t end_pos = str.size() - 1;

	if(str.at(end_pos) == c) {
		str.erase(end_pos);
		return true;
	}

	return false;
}


/*
 * Strips all whitespaces from the start and the end of a string
 */

inline void trim(std::string &str) {

	while(str.size()) {

		if(str.at(str.size() - 1) == ' '  ||
		   str.at(str.size() - 1) == '\t' ||
		   str.at(str.size() - 1) == '\n') {

				str.erase(str.size() - 1);
		} else {
			break;
		}
	}

	while(str.size()) {
		
		if(str.at(0) == ' '  ||
		   str.at(0) == '\t' ||
		   str.at(0) == '\n') {

			str = str.substr(1, str.size() - 1);
		} else {
			break;
		}
	}

}


/*
 * Splits the string when <splchr> is found
 */

inline std::vector<std::string> split(const std::string &str, const char splchr = ' ') {

	std::stringstream ss(str);
	std::string item;
	std::vector<std::string> output;

	while (getline(ss, item, splchr)) {

		if(item.size()) {
			output.emplace_back(std::move(item));
		}
	}

	return output;
}


/*
 * Concatenate multiple variables of different types into a single string
 */

inline std::string concat(std::stringstream &ss) {
	return ss.str();
}
template<typename First, typename ... Many>
inline std::string concat(std::stringstream &ss, const First &arg, const Many &... rest) {
	ss << arg;
	return concat(ss, rest...);
}
template<typename First, typename ... Many>
inline std::string concat(const First &arg, const Many &... rest) {
	std::stringstream ss;
	ss << arg;
	return concat(ss, rest...);
}


/*
 * Safer version of bzbstring::replace
 * Internal function called automatically by bzbstring::replace
 * Do not use
 */

namespace internal {

inline void replace_safe(std::string &target, const std::string &replaced,
					const std::string &replacement, size_t n = 0) {

	std::string result = target;
	int displacement = 0;

	const size_t replaced_length = replaced.size();
	size_t position = -1;
	size_t iterations = 0;

	while ((position = target.find(replaced, position + 1)) != std::string::npos) {

		result.replace(position + displacement, replaced_length, replacement);
		displacement += replacement.size() - replaced_length;
		iterations++;

		if (iterations == n) {
			break;
		}
	}

	target = result;
}

}


/*
 * Replaces all occurrences of <replaced> with <replacement>
 * Use std::replace for single characters
 */

inline void replace(std::string &target, const std::string &replaced,
					const std::string &replacement, size_t n = 0) {

	if(!target.size() || !replaced.size())
		return;

	if(replacement.find(replaced) != std::string::npos) {
		internal::replace_safe(target, replaced, replacement, n);
		return;
	}

	const size_t replaced_length = replaced.size();

	size_t position = -1;
	size_t iterations = 0;

	while ((position = target.find(replaced, position + 1)) != std::string::npos) {

		target.replace(position, replaced_length, replacement);
		iterations++;

		if (iterations == n) {
			break;
		}
	}
}


/*
 * Replace all backslashes with slashes
 */

inline std::string to_unix_path(std::string path) {

	std::replace(path.begin(), path.end(), '\\', '/');
	return path;
}


/*
 * Convert a byte to hex in a string
 */

inline std::string hex(unsigned char c) {

	unsigned char hextable[] = "0123456789abcdef";
	std::string res;
	res += hextable[(c >> 4) & 0x0F];
	res += hextable[c & 0x0F];
	return res;
}


/*
 * Move binary data into an int (little endian)
 */

inline void readint(const std::vector<char> &data, size_t offset, uint16_t* dest) {
	uint16_t res = 0;
	char* ptr = (char*) &res;
	ptr[0] = data[offset];
	ptr[1] = data[offset + 1];
	*dest = res;
}

inline void readint(const std::vector<char> &data, size_t offset, uint32_t* dest) {
	uint32_t res = 0;
	char* ptr = (char*) &res;
	ptr[0] = data[offset];
	ptr[1] = data[offset + 1];
	ptr[2] = data[offset + 2];
	ptr[3] = data[offset + 3];
	*dest = res;
}

inline void readint(const std::vector<char> &data, size_t offset, uint64_t* dest) {
	uint64_t res = 0;
	char* ptr = (char*) &res;
	ptr[0] = data[offset];
	ptr[1] = data[offset + 1];
	ptr[2] = data[offset + 2];
	ptr[3] = data[offset + 3];
	ptr[4] = data[offset + 4];
	ptr[5] = data[offset + 5];
	ptr[6] = data[offset + 6];
	ptr[7] = data[offset + 7];
	*dest = res;
}

// Big endian functions

inline void readint_be(const std::vector<char> &data, size_t offset, uint16_t* dest) {
	uint16_t res = 0;
	char* ptr = (char*) &res;
	ptr[1] = data[offset];
	ptr[0] = data[offset + 1];
	*dest = res;
}

inline void readint_be(const std::vector<char> &data, size_t offset, uint32_t* dest) {
	uint32_t res = 0;
	char* ptr = (char*) &res;
	ptr[3] = data[offset];
	ptr[2] = data[offset + 1];
	ptr[1] = data[offset + 2];
	ptr[0] = data[offset + 3];
	*dest = res;
}

inline void readint_be(const std::vector<char> &data, size_t offset, uint64_t* dest) {
	uint64_t res = 0;
	char* ptr = (char*) &res;
	ptr[7] = data[offset];
	ptr[6] = data[offset + 1];
	ptr[5] = data[offset + 2];
	ptr[4] = data[offset + 3];
	ptr[3] = data[offset + 4];
	ptr[2] = data[offset + 5];
	ptr[1] = data[offset + 6];
	ptr[0] = data[offset + 7];
	*dest = res;
}


}
